#include "Precompiled.h"
#include "Renderer.h"

#define SmoothingStep			0.3f
#define SmoothingStepSq			0.09f
#define PointDensityPerScreen	360


static UInt16Const g_u64CriticalHeatRate = 10;
static UInt16Const g_u64MaxAllowedHeatRate = 5;
static UInt16 g_u64RendererHeatRate = 0;

void Demo::Renderer::OnExit()
{
	m_Chain.wait();
	m_RenderingCycles.wait();
}


Demo::Renderer::Renderer() : 
	m_BlackBrush(nullptr),
	m_GreenBrush(nullptr),
	m_BlueBrush(nullptr),
	m_RedBrush(nullptr),
	m_f2Scaling(1.0f),
	m_f2Offset(0.0f)
{
	m_RenderingCycles = CreateAsync([]{});
	m_Chain = CreateAsync([]{});
	m_bChainComplete = true;
	m_bUpdateTarget = true;
	m_bDrawMsPtrInfo = false;
	m_bDrawing = false;
}

void Demo::Renderer::OnWindowsSizeChanged()
{
	DirectXBase::UpdateForWindowSizeChange();

	RECT WndRct;

#ifdef REIGN_DESKTOP
	GetClientRect( 
		m_Window, 
		&WndRct 
		);

#else
	WndRct.bottom = m_Window->BoundingRect.Bottom;
	WndRct.right = m_Window->BoundingRect.Right;
	WndRct.left = m_Window->BoundingRect.Left;
	WndRct.top = m_Window->BoundingRect.Top;

#endif
	m_HUDTxtRct = D2D1::RectF(
		static_cast<Float32>(WndRct.left) + 15.0f,
		static_cast<Float32>(WndRct.top) + 10.0f,
		static_cast<Float32>(WndRct.right - WndRct.left),
		static_cast<Float32>(WndRct.bottom - WndRct.top)
		);

	m_f2Ptr.x = WndRct.right * 0.5f;
	m_f2Ptr.y = WndRct.bottom * 0.5f;
}

void Demo::Renderer::CreateResources()
{
	m_BlackBrush = nullptr;
	m_GreenBrush = nullptr;
	m_BlueBrush = nullptr;
	m_RedBrush = nullptr;
	m_HUDTxtFmt = nullptr;

	Sehnsucht::ThrowIfFailed( 
		DirectXBase::m_Context2->CreateSolidColorBrush(
			D2D1::ColorF(D2D1::ColorF::LightBlue), 
			m_BlueBrush.GetAddressOf()
			)
		);
	Sehnsucht::ThrowIfFailed( 
		DirectXBase::m_Context2->CreateSolidColorBrush(
			D2D1::ColorF(D2D1::ColorF::Red), 
			m_RedBrush.GetAddressOf()
			)
		);
	Sehnsucht::ThrowIfFailed( 
		DirectXBase::m_Context2->CreateSolidColorBrush(
			D2D1::ColorF(D2D1::ColorF::LightGreen), 
			m_GreenBrush.GetAddressOf()
			)
		);
	Sehnsucht::ThrowIfFailed( 
		DirectXBase::m_Context2->CreateSolidColorBrush(
			D2D1::ColorF(D2D1::ColorF::DarkSlateBlue), 
			m_BlackBrush.GetAddressOf()
			)
		);
	Sehnsucht::ThrowIfFailed( 
		DirectXBase::m_Context2->CreateSolidColorBrush(
			D2D1::ColorF(D2D1::ColorF::Orange), 
			m_OrangeBrush.GetAddressOf()
			)
		);
	Sehnsucht::ThrowIfFailed( 
		DirectXBase::m_Context2->CreateSolidColorBrush(
			D2D1::ColorF(D2D1::ColorF::LightPink), 
			m_PinkBrush.GetAddressOf()
			)
		);
	Sehnsucht::ThrowIfFailed( 
		DirectXBase::m_Context2->CreateSolidColorBrush(
			D2D1::ColorF(D2D1::ColorF::Yellow), 
			m_YellowBrush.GetAddressOf()
			)
		);

	Sehnsucht::ThrowIfFailed( 
		DirectXBase::m_DwFactory2->CreateTextFormat(
			L"Consolas", nullptr, DWRITE_FONT_WEIGHT_REGULAR,
			DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
			20.0f, L"en-us", m_HUDTxtFmt.GetAddressOf()
			)
		);
	Sehnsucht::ThrowIfFailed( 
		DirectXBase::m_DwFactory2->CreateTextFormat(
			L"Consolas", nullptr, DWRITE_FONT_WEIGHT_REGULAR,
			DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
			16.0f, L"en-us", m_MsPtrTxtFmt.GetAddressOf()
			)
		);

	
		/*Demo::Renderer::SolidColorBrush m_YellowBrush;
		Demo::Renderer::SolidColorBrush m_OrangeBrush;
		Demo::Renderer::SolidColorBrush m_PinkBrush;*/

	{
		//float dashes[] = {1.0f, 2.0f, 2.0f, 3.0f, 2.0f, 2.0f};

		auto DashDot = D2D1::StrokeStyleProperties(
            D2D1_CAP_STYLE_ROUND,
            D2D1_CAP_STYLE_ROUND,
            D2D1_CAP_STYLE_ROUND,
            D2D1_LINE_JOIN_MITER,
            10.0f,
            D2D1_DASH_STYLE_DASH_DOT,
            0.0f
			);

		Sehnsucht::ThrowIfFailed( 
			DirectXBase::m_Factory2->CreateStrokeStyle(
				DashDot, nullptr, 0, //dashes, ARRAYSIZE(dashes),
				m_DashDotStyle.GetAddressOf()
				)
			);

		auto Dash = D2D1::StrokeStyleProperties(
            D2D1_CAP_STYLE_ROUND,
            D2D1_CAP_STYLE_ROUND,
            D2D1_CAP_STYLE_ROUND,
            D2D1_LINE_JOIN_MITER,
            10.0f,
            D2D1_DASH_STYLE_DASH,
            0.0f
			);

		Sehnsucht::ThrowIfFailed( 
			DirectXBase::m_Factory2->CreateStrokeStyle(
				Dash, nullptr, 0, //dashes, ARRAYSIZE(dashes),
				m_DashStyle.GetAddressOf()
				)
			);

		auto Dot = D2D1::StrokeStyleProperties(
            D2D1_CAP_STYLE_ROUND,
            D2D1_CAP_STYLE_ROUND,
            D2D1_CAP_STYLE_ROUND,
            D2D1_LINE_JOIN_MITER,
            10.0f,
            D2D1_DASH_STYLE_DOT,
            0.0f
			);

		Sehnsucht::ThrowIfFailed( 
			DirectXBase::m_Factory2->CreateStrokeStyle(
				Dot, nullptr, 0, //dashes, ARRAYSIZE(dashes),
				m_DotStyle.GetAddressOf()
				)
			);
	}

	{
		IWICBitmapDecoder *pDecoder = NULL;
		IWICBitmapFrameDecode *pSource = NULL;
		IWICStream *pStream = NULL;
		IWICFormatConverter *pConverter = NULL;
		IWICBitmapScaler *pScaler = NULL;

		HRESULT hr = m_WicFactory2->CreateDecoderFromFilename(
			L"StarSketchForLabOpaqueClipped.png",
			NULL,
			GENERIC_READ,
			WICDecodeMetadataCacheOnLoad,
			&pDecoder
			);

		if (SUCCEEDED(hr))
		{
			// Create the initial frame.
			hr = pDecoder->GetFrame(0, &pSource);
		}

		if (SUCCEEDED(hr))
		{

			// Convert the image format to 32bppPBGRA
			// (DXGI_FORMAT_B8G8R8A8_UNORM + D2D1_ALPHA_MODE_PREMULTIPLIED).
			hr = m_WicFactory2->CreateFormatConverter(&pConverter);

		}

		if (SUCCEEDED(hr))
		{
			hr = pConverter->Initialize(
				pSource,
				GUID_WICPixelFormat32bppPBGRA,
				WICBitmapDitherTypeNone,
				NULL,
				0.f,
				WICBitmapPaletteTypeMedianCut
				);
		}


		if (SUCCEEDED(hr))
		{
    
			// Create a Direct2D bitmap from the WIC bitmap.
			hr = m_Context2->CreateBitmapFromWicBitmap(
				pConverter, NULL,
				m_StartBitmap.GetAddressOf()
				);
		}

		Safe_Release(pDecoder);
		Safe_Release(pSource);
		Safe_Release(pStream);
		Safe_Release(pConverter);
		Safe_Release(pScaler);
	}


	OnWindowsSizeChanged();

	m_HUDTxt = L"GM Labwork 6\nVlad Serhiienko\n";

}

void Demo::Renderer::OnFrameMove( _In_ Demo::TimerViewPtr pTimer )
{
	Char16 Data[512];

	swprintf_s(
		Data, L"GM Labwork 6 > Vlad Serhiienko CS33 > Framerate %d Overheat %.1f" 
		L"\nOffset %5.5f %5.5f\tZoom %5.5f %5.5f",
		pTimer->Framerate, (float)g_u64RendererHeatRate / (float)g_u64MaxAllowedHeatRate,
		m_f2Offset.x, m_f2Offset.y, m_f2Scaling.x, m_f2Scaling.y
		);

	m_HUDTxt = Data;
}

void Demo::Renderer::OnPointerPressed( _In_ Demo::Events::AppWindowPointerEventArgsPtr pArgs )
{
	m_f2Ptr.x = pArgs->CurrentPoint->Position->X;
	m_f2Ptr.y = pArgs->CurrentPoint->Position->Y;
}

void Demo::Renderer::OnPointerWheelChanged( _In_ Demo::Events::AppWindowPointerEventArgsPtr pArgs )
{

	if ( m_bChainComplete )
	{
		auto iDelta = (Float32)pArgs->CurrentPoint->Properties->MouseWheelDelta;
		m_Chain = m_Chain.then([this, iDelta]
		{
			m_bChainComplete = false;

			if ( iDelta < 0 )
			{
				m_f2Scaling.x *= 1.5f;
				m_f2Scaling.y *= 1.5f;
			}
			else
			{
				m_f2Scaling.x /= 1.5f;
				m_f2Scaling.y /= 1.5f;
			}

			m_bChainComplete = true;
			m_bUpdateTarget = true;
		});
	}
}

void Demo::Renderer::OnPointerMoved( _In_ Demo::Events::AppWindowPointerEventArgsPtr pArgs )
{
	if ( pArgs->CurrentPoint->Properties->IsLeftButtonPressed )
	{
		if ( m_bChainComplete )
		{
			Reign::Vector2 f2NewPtr(0.f);

#ifdef REIGN_DESKTOP
			f2NewPtr.x = pArgs->CurrentPoint->Position->X;
			f2NewPtr.y = pArgs->CurrentPoint->Position->Y;
#else
			f2NewPtr.x = pArgs->CurrentPoint->Position.X;
			f2NewPtr.y = pArgs->CurrentPoint->Position.Y;
#endif

			m_Chain = m_Chain.then([this, f2NewPtr]
			{
				m_bChainComplete = false;

				m_f2Offset.x += (f2NewPtr.x - m_f2Ptr.x) * SmoothingStepSq * m_f2Scaling.x;
				m_f2Offset.y += (f2NewPtr.y - m_f2Ptr.y) * SmoothingStepSq * m_f2Scaling.y;
				m_f2Ptr = f2NewPtr;

				m_ActiveRegion.right = -(m_RenderTargetSize.width * -0.5f + m_f2Offset.x);
				m_ActiveRegion.left = -(m_RenderTargetSize.width * 0.5f + m_f2Offset.x);

				m_ActiveRegion.top = m_RenderTargetSize.height * 0.5f + m_f2Offset.y;
				m_ActiveRegion.bottom = m_RenderTargetSize.height * -0.5f + m_f2Offset.y;

				{
					auto f2Screen = ToScreenCoords( f2NewPtr );

					Char16 wszData[MAX_PATH];
					swprintf_s( wszData, 
						L"Coords\n"
							L" %.1f" L" %.1f\n"
						L"Region\n"
							L" %.2f" L" %.2f\n"
							L" %.2f" L" %.2f",
						f2NewPtr.x, f2NewPtr.y, f2Screen.x, f2Screen.y,
						m_ActiveRegion.left, m_ActiveRegion.top, 
						m_ActiveRegion.right, m_ActiveRegion.bottom
						);

					m_MsPtrTxt = wszData;

					//PrintToDebugWindowOnly16( m_MsPtrTxt.c_str() );
				}

				m_bChainComplete = true;
				m_bUpdateTarget = true;
				//m_bDrawGrid = true;
			});
		}

		m_MsPtrTxtRct = D2D1::RectF(
			m_f2Ptr.x + 4.0f, m_f2Ptr.y + 8.0f, 
			m_RenderTargetSize.width, 
			m_RenderTargetSize.height
			);

		m_bDrawMsPtrInfo = true;
	}
}

void Demo::Renderer::DrawGrid()
{
	float fXLength = m_ActiveRegion.right - m_ActiveRegion.left, fYLength = m_ActiveRegion.top - m_ActiveRegion.bottom;
	//float fXBigStep = fXLength / 10.0f, fXLittleStep = fXLength / 20.0f;
	//float fYBigStep = fYLength / 10.0f, fYLittleStep = fYLength / 20.0f;

	float fXStart = floor(m_ActiveRegion.left) - fXLength * 0.5f; //, // - m_f2Offset.x, // / m_f2Scaling.x,
		//fXEnd = ceil(m_ActiveRegion.right) - fXLength * 0.5f;// - m_f2Offset.x; // / m_f2Scaling.x;
	float fYStart = floor(m_ActiveRegion.bottom) + fYLength * 0.5f; //, // + m_f2Offset.y, // / m_f2Scaling.y,
		//fYEnd = ceil(m_ActiveRegion.top) + fYLength * 0.5f;// + m_f2Offset.y; // / m_f2Scaling.y;

	m_Context2->DrawLine(
		D2D1::Point2F(-fXStart - fXLength * 0.5f, 0.0f),
		D2D1::Point2F(-fXStart - fXLength * 0.5f, m_RenderTargetSize.height),
		m_OrangeBrush.Get(), 2.0f, 
		m_DashDotStyle.Get()
		);

	m_Context2->DrawLine(
		D2D1::Point2F(0.0f, fYStart + fYLength * 0.5f),
		D2D1::Point2F(m_RenderTargetSize.width, fYStart + fYLength * 0.5f),
		m_OrangeBrush.Get(), 2.0f, 
		m_DashDotStyle.Get()
		);

	//for ( auto fX = fXStart; fX < fXEnd; fX += fXBigStep )
	//{
	//	m_Context2->DrawLine(
	//		D2D1::Point2F(-fX, 0.0f),
	//		D2D1::Point2F(-fX, m_RenderTargetSize.height),
	//		m_BlueBrush.Get(), 1.0f //, 
	//		//m_DashStyle.Get()
	//		);
	//}

	//for ( auto fY = fYStart; fY < fYEnd; fY += fYBigStep )
	//{
	//	m_Context2->DrawLine(
	//		D2D1::Point2F(0.0f, fY),
	//		D2D1::Point2F(m_RenderTargetSize.width, fY),
	//		m_BlueBrush.Get(), 1.0f //, 
	//		//m_DashStyle.Get()
	//		);
	//}

	//m_bDrawGrid = false;
}

void Demo::Renderer::BeginDraw2()
{
	m_Context2->BeginDraw();

    m_Context2->SetTransform(
		D2D1::Matrix3x2F::Identity()
		);

    m_Context2->Clear(D2D1::ColorF(
		D2D1::ColorF::DarkGray
		));

	DrawGrid();
}

void Demo::Renderer::EndDraw2()
{
	m_Context2->EndDraw();
}

void Demo::Renderer::OnFrame()
{
	static float sfAngle = 0.0f;

	// We obviously dont want to redraw anything
	// unless any changes are pending

	if ( !m_bUpdateTarget || !m_bChainComplete ) return;
	//else PrintToDebugWindowOnly8( "Ignore cycle" );

	if ( m_bDrawing )  
	{
		// To not append a rendering cycle unless 
		// the previous one is still in action
		return;
	}
	else
	{
		// We want to know how much cycles are 
		// now in the queue 

		g_u64RendererHeatRate++;

		if ( g_u64RendererHeatRate > g_u64MaxAllowedHeatRate )
		{
#ifndef GMLabwork07Implementation
			PrintToDebugWindowOnly8(  "OVERHEAT" );
#endif
		}
		else if ( g_u64RendererHeatRate > g_u64CriticalHeatRate )
		{
			PrintToDebugWindowOnly8(  "CRITICAL" );
			m_RenderingCycles.wait();
			g_u64RendererHeatRate = 0;
		}
	}

	m_RenderingCycles = m_RenderingCycles.then([this]
	{
		//PrintToDebugWindowOnly8( "Render cycle" );
		m_bDrawing = true;
		BeginDraw2();

		m_Context2->DrawTextW(
			m_HUDTxt.data(), m_HUDTxt.size(),
			m_HUDTxtFmt.Get(), m_HUDTxtRct,
			m_YellowBrush.Get()
			);

		m_bUpdateTarget = false;

		if ( m_bDrawMsPtrInfo )
		{
			m_Context2->DrawText(
				m_MsPtrTxt.data(), m_MsPtrTxt.size(),
				m_MsPtrTxtFmt.Get(), m_MsPtrTxtRct,
				m_YellowBrush.Get()
				);

			// Do not show pointer info again
			m_bDrawMsPtrInfo = false;

			// Redraw to remove the rendered text
			m_bUpdateTarget = true;
		}

#if GMLabwork06Implementation

		Reign::Vector2 f2Range(-3.2f, 3.2f);

		auto pCosHFunc = []( Float32 x) 
		{ 
			return coshf(x); 
		};

		DrawFunction(
			f2Range, 18, pCosHFunc, m_BlackBrush.Get()//,
			//2.5f, m_DotStyle.Get()
			); 

		auto pTeilor3Func = []( Float32 x) 
		{ 
			return 1 + x * x * 0.5f + x * x * x * x / 2 / 3 / 4; 
		};

		DrawFunction(
			f2Range, 18, pTeilor3Func, m_GreenBrush.Get()//,
			//2.5f, m_DashStyle.Get()
			); 

		auto pTeilor4Func = []( Float32 x) 
		{ 
			return 1 + x * x / 2 + x * x * x * x / 2 / 3 / 4 + x * x * x * x * x * x / 2 / 3 / 4 / 5 / 6; 
		};

		DrawFunction(
			f2Range, 18, pTeilor4Func, m_RedBrush.Get()//,
			//2.5f, m_DashDotStyle.Get()
			); 

#elif GMLabwork08Implementation 

		{
			D2D1_POINT_2F af2Points[] = 
			{
				{ 15.0f,	15.0f },
				{  4.0f,	 2.0f },
				{  16.0f,	 8.0f },
				{  8.0f,	17.0f },
				{  3.0f,	 3.0f },
				{  8.0f,	 9.0f },
			};

			DrawBezier(af2Points, ARRAYSIZE(af2Points),
				m_YellowBrush.Get(), m_PinkBrush.Get(),
				1.8f, 2.8f, nullptr, 
				m_DotStyle.Get()
				);

			/*DrawLines(
				af2Points, ARRAYSIZE(af2Points), 
				m_GreenBrush.Get()
				);*/

			/*for ( auto &Pnt : af2Points )
			{
				ToWindowCoords( Pnt, Pnt );
			}*/
		}


#elif GMLabwork07Implementation
		
		auto f2Center = D2D1::Point2F(0.0f, 0.0f);
		ToWindowCoords( f2Center, f2Center );

		m_Context2->SetTransform(
			D2D1::Matrix3x2F::Rotation(-sfAngle, f2Center) * 
			D2D1::Matrix3x2F::Translation(sinf(sfAngle/10) * 150, cosf(sfAngle/10) * 150)
			);

		m_Context2->DrawBitmap(
			m_StartBitmap.Get(), 
			D2D1::RectF(
				f2Center.x - 25 * m_f2Scaling.x, f2Center.y - 25 * m_f2Scaling.x, 
				f2Center.x + 25 * m_f2Scaling.x, f2Center.y + 25 * m_f2Scaling.x 
				)
			);

		m_Context2->SetTransform(
			D2D1::Matrix3x2F::Rotation((-sfAngle+5), f2Center) * 
			D2D1::Matrix3x2F::Translation(sinf((sfAngle-5)/10) * 150, cosf((sfAngle-5)/10) * 150)
			);

		m_Context2->DrawBitmap(
			m_StartBitmap.Get(), 
			D2D1::RectF(
				f2Center.x - 25 * m_f2Scaling.x, f2Center.y - 25 * m_f2Scaling.x, 
				f2Center.x + 25 * m_f2Scaling.x, f2Center.y + 25 * m_f2Scaling.x 
				),
			0.7f,
			D2D1_BITMAP_INTERPOLATION_MODE_LINEAR
			);

		m_Context2->SetTransform(
			D2D1::Matrix3x2F::Rotation((-sfAngle+10), f2Center) * 
			D2D1::Matrix3x2F::Translation(sinf((sfAngle-10)/10) * 150, cosf((sfAngle-10)/10) * 150)
			);

		m_Context2->DrawBitmap(
			m_StartBitmap.Get(), 
			D2D1::RectF(
				f2Center.x - 25 * m_f2Scaling.x, f2Center.y - 25 * m_f2Scaling.x, 
				f2Center.x + 25 * m_f2Scaling.x, f2Center.y + 25 * m_f2Scaling.x 
				),
			0.5f,
			D2D1_BITMAP_INTERPOLATION_MODE_LINEAR
			);

		m_Context2->SetTransform(
			D2D1::Matrix3x2F::Rotation((-sfAngle+15), f2Center) * 
			D2D1::Matrix3x2F::Translation(sinf((sfAngle-15)/10) * 150, cosf((sfAngle-15)/10) * 150)
			);

		m_Context2->DrawBitmap(
			m_StartBitmap.Get(), 
			D2D1::RectF(
				f2Center.x - 25 * m_f2Scaling.x, f2Center.y - 25 * m_f2Scaling.x, 
				f2Center.x + 25 * m_f2Scaling.x, f2Center.y + 25 * m_f2Scaling.x 
				),
			0.2f,
			D2D1_BITMAP_INTERPOLATION_MODE_LINEAR
			);

		m_Context2->SetTransform(
			D2D1::Matrix3x2F::Rotation((-sfAngle+20), f2Center) * 
			D2D1::Matrix3x2F::Translation(sinf((sfAngle-20)/10) * 150, cosf((sfAngle-20)/10) * 150)
			);

		m_Context2->DrawBitmap(
			m_StartBitmap.Get(), 
			D2D1::RectF(
				f2Center.x - 25 * m_f2Scaling.x, f2Center.y - 25 * m_f2Scaling.x, 
				f2Center.x + 25 * m_f2Scaling.x, f2Center.y + 25 * m_f2Scaling.x 
				),
			0.1f,
			D2D1_BITMAP_INTERPOLATION_MODE_LINEAR
			);

		sfAngle += 1.f;


		if ( sfAngle >= 360.0f ) 
			sfAngle = 0.0f;

		m_bUpdateTarget = true;

		/*m_Context2->SetTransform(
			D2D1::Matrix3x2F::Rotation(20, f2Center)
			);*/

#endif

		

		EndDraw2();
		Present();

		m_bDrawing = false;
		g_u64RendererHeatRate--;
	});
}